namespace Tnelab{
    let InvokeNativeWebSocket: WebSocket = null;
    let InvokeNativeIsAlive: boolean = false;
    let InvokeNativeReturnHandles: Array<{ InvokeId: Number, Handle: (ArrayBuffer) => void }> = new Array<{ InvokeId: Number, Handle: (ArrayBuffer) => void }>();
    function InvokeNativeHandle(data: ArrayBuffer): void {
        let serializer = new Tnelab.ContextSerializer();
        let view = new DataView(data);
        let position = { Offset: 0 };
        let id = serializer.FromBytes("System.Int32", "number", view, position);
        let info: { InvokeId: Number, Handle: (ArrayBuffer) => void } = null;
        for (let i = 0; i < InvokeNativeReturnHandles.length; i++) {
            if (InvokeNativeReturnHandles[i].InvokeId === id) {
                info = InvokeNativeReturnHandles[i]
                InvokeNativeReturnHandles.splice(i, 1);
                break;
            }
        }
        if (info === null) {
            return;
        }
        info.Handle(data);
    }
    export function WaitInvokeNative(url: string): Promise<void> {
        return new Promise((resolve, reject) => {
            if (InvokeNativeIsAlive) {
                resolve();
                return;
            }
            let ws = new WebSocket(url);
            ws.binaryType = 'arraybuffer';
            ws.onopen = function (e) {
                InvokeNativeWebSocket = ws;
                InvokeNativeIsAlive = true;
                resolve();
            };
            ws.onclose = function (e) {
                InvokeNativeIsAlive = false;
            };
            ws.onmessage = function (e) {
                InvokeNativeHandle(e.data);
            };
            ws.onerror = function (e) {
                throw "WebSocket Error:" + url;
            };
        });
    }
    function GetPortForTneJsCall(): number {
        return parseInt(location.port);
    }
    export async function InvokeNative(servicePath: string, methodName: string, argInfos: Array<{ CSTypeName: string, JSTypeName: string, Value: any }>, actionOrFuncArgInfosList: Array<Array<{ CSTypeName: string, JSTypeName: string }>>): Promise<any> {
        if (PortForTneJsCall == 0) {
            PortForTneJsCall=GetPortForTneJsCall();
        }
        let port = PortForTneJsCall;
        await WaitInvokeNative("ws://127.0.0.1:" + port + "/InvokeNative");
        return new Promise(async (resolve, reject) => {
            let id = Tnelab.GetRandomNum(1000000, 9999999);
            let serializer = new Tnelab.ContextSerializer();
            let bytes = new Uint8Array(10 * 1024 * 1024);
            let positonForTo = { Offset: 0 };
            serializer.ToBytes("System.Int32", id, bytes, positonForTo);
            serializer.ToBytes("System.String", servicePath + "." + methodName, bytes, positonForTo);
            let afIndex = 0;
            for (let i = 1; i < argInfos.length; i++) {
                if (argInfos[i].CSTypeName.startsWith("System.Func<") || argInfos[i].CSTypeName.startsWith("System.Action")) {
                    await Tnelab.WaitNativeInvoke("ws://127.0.0.1:" + port + "/NativeInvoke");
                    let afArgInfos = new Array<{ CSTypeName: string, JSTypeName: String }>();
                    let isFunc = argInfos[i].CSTypeName.startsWith("System.Func<") ? true : false;
                    let subArgsInfo = actionOrFuncArgInfosList[afIndex];
                    afIndex++;
                    let callback = argInfos[i].Value;
                    let funcName = "Tnelab_MBWpfDemo_Service_TestService_DevFunc_" + id + "_" + afIndex + "_";
                    let func = async (functionName: string, afRDatas: ArrayBuffer): Promise<void> => {
                        await Tnelab.FunctionOrActionCall(functionName, afRDatas, callback, port, isFunc, subArgsInfo);
                    }
                    eval("window." + funcName + "=func");
                    serializer.ToBytes(argInfos[i].CSTypeName, funcName, bytes, positonForTo);
                }
                else {
                    serializer.ToBytes(argInfos[i].CSTypeName, argInfos[i].Value, bytes, positonForTo);
                }
            }
            let datas = new Uint8Array(positonForTo.Offset);
            for (let i = 0; i < datas.length; i++) {
                datas[i] = bytes[i];
            }
            bytes = null;
            let info = {
                InvokeId: id, Handle: function (rdatas) {
                    let serializer_ = new Tnelab.ContextSerializer();
                    let view_ = new DataView(rdatas);
                    let positon_ = { Offset: 0 };
                    let id_ = serializer_.FromBytes("System.Int32", "number", view_, positon_);
                    let rbool_ = serializer_.FromBytes("System.Boolean", "boolean", view_, positon_);
                    if (rbool_ === false) {
                        let errorMessage_ = serializer_.FromBytes("System.String", "string", view_, positon_);
                        let error_ = "调用异常:" + errorMessage_;
                        alert(error_);
                        throw error_;
                    }
                    if (argInfos[0].JSTypeName === "void") {
                        resolve(undefined);
                        return;
                    }
                    let robj_ = serializer_.FromBytes(argInfos[0].CSTypeName, argInfos[0].JSTypeName, view_, positon_);
                    resolve(robj_);
                }
            };
            InvokeNativeReturnHandles.push(info);
            InvokeNativeWebSocket.send(datas);
        });
    }
    let NativeInvokeWebSocket: WebSocket = null;
    export let PortForTneJsCall: number = 0;
    function NativeInvokeHandle(data) {
        let serializer = new Tnelab.ContextSerializer();
        let view = new DataView(data);
        let position = { Offset: 0 };
        //let id = serializer.FromBytes("System.Int32", "number", view, position);
        let funcName = serializer.FromBytes("System.String", "string", view, position);
        if (funcName === "clear") {
            funcName = serializer.FromBytes("System.String", "string", view, position);
            let script = "delete window." + funcName;
            eval(script);
            return;
        }
        eval("window." + funcName + "(\"" + funcName + "\",data)");
    }
    let NativeInvokeIsAlive: boolean = false;
    export function WaitNativeInvoke(url: string): Promise<void> {
        return new Promise((resolve, reject) => {
            if (NativeInvokeIsAlive) {
                resolve();
                return;
            }
            let ws = new WebSocket(url);
            ws.binaryType = 'arraybuffer';
            ws.onopen = function (e) {
                NativeInvokeWebSocket = ws;
                NativeInvokeIsAlive = true;
                resolve();
            };
            ws.onclose = function (e) {
                NativeInvokeIsAlive = false;
            };
            ws.onmessage = function (e) {
                NativeInvokeHandle(e.data);
            };
            ws.onerror = function (e) {
                throw "WebSocket Error:" + url;
            };
        });
    }
    export async function FunctionOrActionCall(functionName: string, rdatas: ArrayBuffer, func: Function, portForTneJsCall: number, isFunc: boolean, argsInfo: Array<{ CSTypeName: string, JSTypeName: string }>): Promise<void> {
        let serializer = new Tnelab.ContextSerializer();
        let view = new DataView(rdatas);
        let position = { Offset: 0 };
/**/
        let funcName = serializer.FromBytes("System.String", "string", view, position);
        let argsCount = argsInfo.length;
        if (isFunc)
            argsCount -= 1;
        let funcArgs;
        if (argsCount != 0) {
            funcArgs = new Array<any>(argsCount);
            for (let i = 0; i < argsCount; i++) {
                funcArgs[i] = serializer.FromBytes(argsInfo[i].CSTypeName, argsInfo[i].JSTypeName, view, position);
            }
        }
        let script = "func(";
        for (let i = 0; i < argsCount; i++) {
            script += "funcArgs[" + i + "],";
        }
        if (argsCount != 0)
            script = script.substr(0, script.length - 1);
        script += ")";

        let result = undefined;
        let scriptExecIsOk = false;
        let csTypeName = "System.String";
        try {
            result = eval(script);
            scriptExecIsOk = true;
            if (isFunc)
                csTypeName = argsInfo[argsInfo.length - 1].CSTypeName;
        }
        catch (error) {
            result = error + "\n" + func;
        }
        let bytes = new Uint8Array(10 * 1024 * 1024);
        let positonForTo = { Offset: 0 };
        serializer.ToBytes("System.Boolean", scriptExecIsOk, bytes, positonForTo);
        if (isFunc || !scriptExecIsOk)
            serializer.ToBytes(csTypeName, result, bytes, positonForTo);
        let datas = new Uint8Array(positonForTo.Offset);
        for (let i = 0; i < datas.length; i++) {
            datas[i] = bytes[i];
        }
        NativeInvokeWebSocket.send(datas);
    }
}
